#define __SHORT_CIRCUIT_ET__
// Need to short circuit macros for min/max/floor/ceil to enable ET
#include "stencil.hpp"

#include <xmmintrin.h>
#include <immintrin.h>

#include "./simple_expression_template/VAR.hpp"
#include "./simple_expression_template/TERM.hpp"
#include "./simple_expression_template/EXPRESSION.hpp"
#include "./simple_expression_template/ADD.hpp"
#include "./simple_expression_template/SUB.hpp"
#include "./simple_expression_template/MIN.hpp"
#include "./simple_expression_template/MAX.hpp"
#include "./simple_expression_template/CEIL.hpp"
#include "./simple_expression_template/FLOOR.hpp"
#include "./simple_expression_template/GE.hpp"
#include "./simple_expression_template/LE.hpp"

#undef __SHORT_CIRCUIT_ET__


template< class DC >
void stencil_tuner::depends( const TileTag & tt, stencil_context & c, DC & dc ) const
{
  assert(size_t(tt) > 0);

  bool condt1 = false;
  bool condt2 = false;
  bool condt3 = false;
  DoublePtrWrapper wA;

  {
    constexpr VariableTerm<1> t1;
    constexpr VariableTerm<2> t2;
    constexpr VariableTerm<3> t3;
    constexpr VariableTerm<4> pT;
    constexpr VariableTerm<5> pM;
    constexpr VariableTerm<6> pN;
    constexpr VariableTerm<7> pP;

    std::tuple<int, int, int, int, int, int, int, int> 
      test_tuple_lb(0, tt[0], tt[1], tt[2], tt[3], tt[4], tt[5], tt[6]);

    auto lb_t1 = (t1>=ceil(-pN-15,16));
    auto ub_t1 = (t1<=floor(pT-3,16));
    auto lb_t2 = (t2>=max(t1,-t1-1));
    auto ub_t2 = (t2<=min(min(floor(-8*t1+pT-2,8),floor(8*t1+pN+7,8)),floor(pT+pN-2,16)));
    auto lb_t3 = (t3>=max(max(0,ceil(t1+t2-1,2)),ceil(16*t2-pN-14,16)));
    auto ub_t3 = (t3<=min(min(floor(pT+pN-2,16),floor(16*t2+pN+14,16)),floor(8*t1+8*t2+pN+15,16)));

    std::get<1>(test_tuple_lb) = tt[0]-1;
    condt1 |= !(lb_t1.Val(test_tuple_lb));
    condt1 |= !(ub_t1.Val(test_tuple_lb));
    condt1 |= !(lb_t2.Val(test_tuple_lb));
    condt1 |= !(ub_t2.Val(test_tuple_lb));
    condt1 |= !(lb_t3.Val(test_tuple_lb));
    condt1 |= !(ub_t3.Val(test_tuple_lb));

    std::get<1>(test_tuple_lb) = tt[0];
    std::get<2>(test_tuple_lb) = tt[1]-1;
    condt2 |= !(lb_t2.Val(test_tuple_lb));
    condt2 |= !(ub_t2.Val(test_tuple_lb));
    condt2 |= !(lb_t3.Val(test_tuple_lb));
    condt2 |= !(ub_t3.Val(test_tuple_lb));

    std::get<2>(test_tuple_lb) = tt[1];
    std::get<3>(test_tuple_lb) = tt[2]-1;
    condt3 |= !(lb_t3.Val(test_tuple_lb));
    condt3 |= !(ub_t3.Val(test_tuple_lb));
  }
  
  int64_t t1 = tt[0];
  int64_t t2 = tt[1];
  int64_t t3 = tt[2];
  int64_t pT = tt[3];
  int64_t pM = tt[4];
  int64_t pN = tt[5];
  int64_t pP = tt[6];
  if (!condt1) {
    TileTag tt2(t1-1,  t2,  t3,  pT,  pN,  pN,  pN);
    dc.depends(c.M, TileTag(t1-1,  t2,  t3,  pT,  pN,  pN,  pN));
  } 
  if (!condt2) {
    TileTag tt2(t1,  t2-1,  t3,  pT,  pN,  pN,  pN);
    dc.depends(c.M, TileTag(t1,  t2-1,  t3,  pT,  pN,  pN,  pN));
  } 
  if (!condt3) {
    TileTag tt2(t1,  t2,  t3-1,  pT,  pN,  pN,  pN);
    dc.depends(c.M, TileTag(t1,  t2,  t3-1,  pT,  pN,  pN,  pN));
  } 
}


int64_t stencil_step::execute(const TileTag &tt, stencil_context & c) const
{

  bool condt1 = false;
  bool condt2 = false;
  bool condt3 = false;
  DoublePtrWrapper wA;

  {
    constexpr VariableTerm<1> t1;
    constexpr VariableTerm<2> t2;
    constexpr VariableTerm<3> t3;
    constexpr VariableTerm<4> pT;
    constexpr VariableTerm<5> pM;
    constexpr VariableTerm<6> pN;
    constexpr VariableTerm<7> pP;

    std::tuple<int, int, int, int, int, int, int, int> 
      test_tuple_lb(0, tt[0], tt[1], tt[2], tt[3], tt[4], tt[5], tt[6]);

    auto lb_t1 = (t1>=ceil(-pN-15,16));
    auto ub_t1 = (t1<=floor(pT-3,16));
    auto lb_t2 = (t2>=max(t1,-t1-1));
    auto ub_t2 = (t2<=min(min(floor(-8*t1+pT-2,8),floor(8*t1+pN+7,8)),floor(pT+pN-2,16)));
    auto lb_t3 = (t3>=max(max(0,ceil(t1+t2-1,2)),ceil(16*t2-pN-14,16)));
    auto ub_t3 = (t3<=min(min(floor(pT+pN-2,16),floor(16*t2+pN+14,16)),floor(8*t1+8*t2+pN+15,16)));

    std::get<1>(test_tuple_lb) = tt[0]-1;
    condt1 |= !(lb_t1.Val(test_tuple_lb));
    condt1 |= !(ub_t1.Val(test_tuple_lb));
    condt1 |= !(lb_t2.Val(test_tuple_lb));
    condt1 |= !(ub_t2.Val(test_tuple_lb));
    condt1 |= !(lb_t3.Val(test_tuple_lb));
    condt1 |= !(ub_t3.Val(test_tuple_lb));

    std::get<1>(test_tuple_lb) = tt[0];
    std::get<2>(test_tuple_lb) = tt[1]-1;
    condt2 |= !(lb_t2.Val(test_tuple_lb));
    condt2 |= !(ub_t2.Val(test_tuple_lb));
    condt2 |= !(lb_t3.Val(test_tuple_lb));
    condt2 |= !(ub_t3.Val(test_tuple_lb));

    std::get<2>(test_tuple_lb) = tt[1];
    std::get<3>(test_tuple_lb) = tt[2]-1;
    condt3 |= !(lb_t3.Val(test_tuple_lb));
    condt3 |= !(ub_t3.Val(test_tuple_lb));
  }

  {
    #include "definitions.h"
    int64_t t1 = tt[0];
    int64_t t2 = tt[1];
    int64_t t3 = tt[2];
    int64_t pT = tt[3];
    int64_t pM = tt[4];
    int64_t pN = tt[5];
    int64_t pP = tt[6];

    if (!condt1) {
      c.M.get(TileTag(t1-1,  t2,  t3,  pT,  pN,  pN,  pN), wA);
    }

    if (!condt2) {
      c.M.get(TileTag(t1,  t2-1,  t3,  pT,  pN,  pN,  pN), wA);
    } 
    if (!condt3) {
      c.M.get(TileTag(t1,  t2,  t3-1,  pT,  pN,  pN,  pN), wA);
    } 

    if (condt1 && condt2 && condt3) {
      c.M.get(TileTag(DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, pT,  pN, pN, pN), wA);
    }

    double (*M)[2][Npadded][Npadded][Npadded] = (double (*)[2][Npadded][Npadded][Npadded])(wA.M);

    int64_t t4, t5, t6, t7, t8, lbv, ubv;
    for (t4=max(max(max(0,ceild(t1+t2-124,125)),ceild(16*t2-pN-998,1000)),ceild(16*t3-pN-998,1000));t4<=min(min(min(min(floord(8*t1+pN+7,500),floord(pT+pN-2,1000)),floord(16*t2+pN+14,1000)),floord(16*t3+pN+14,1000)),floord(8*t1+8*t2+pN+15,1000));t4++) {
      for (t5=max(max(max(max(max(0,8*t1+8*t2),16*t1+1),16*t2-pN),16*t3-pN),1000*t4-pN);t5<=min(min(min(min(min(pT-2,16*t2+14),16*t3+14),1000*t4+998),8*t1+8*t2+15),16*t1+pN+15);t5++) {
        if (t5%2 == 0) {
          for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
            for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
              lbv=max(1000*t4,t5+1);
              ubv=min(1000*t4+999,t5+pN);
#pragma vector always
#pragma ivdep
              for (t8=lbv;t8<=ubv;t8++) {
                M[0][1][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][0][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7][-t5+t8+1])+M[0][0][-t5+t6][-t5+t7][-t5+t8];;
              }
            }
          }
        }else{
          for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
            for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
              lbv=max(1000*t4,t5+1);
              ubv=min(1000*t4+999,t5+pN);
#pragma ivdep
#pragma vector always
              for (t8=lbv;t8<=ubv;t8++) {
                M[0][0][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][1][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7][-t5+t8+1])+M[0][1][-t5+t6][-t5+t7][-t5+t8];;
              }
            }
          }
        }
      }
    }
    c.M.put(TileTag(t1,  t2,  t3,  pT,  pM,  pN,  pP), M);  
  }  
  return CnC::CNC_Success;
}


// int64_t stencil_step::execute(const TileTag &tt, stencil_context & c) const
// {
//   int64_t t1 = tt[0];
//   int64_t t2 = tt[1];
//   int64_t t3 = tt[2];
//   int64_t pT = tt[3];
//   int64_t pM = tt[4];
//   int64_t pN = tt[5];
//   int64_t pP = tt[6];

//   DoublePtrWrapper wA;

//   // The condition is really: is the predecessor along each dimension
//   // in the space or in the environment ?
//   // If the condition is true, the predecessor is outside !
//   bool condt1 = 
//     (t1-1 < ceild(-pN-15,16)) ||
//     (t2<max(t1-1,-(t1-1)-1 )) ||
//     (t2 > min(min(floord(-8*(t1-1)+pT-2,8),floord(8*(t1-1)+pN+7,8)),floord(pT+pN-2,16))) ||
//     (t3 < max(max(0,ceild((t1-1)+t2-1,2)),ceild(16*t2-pN-14,16))) || 
//     (t3 >min(min(floord(pT+pN-2,16),floord(16*t2+pN+14,16)),floord(8*(t1-1)+8*t2+pN+15,16)));
//   bool condt2 = 
//     ((t2-1) < max(t1,-t1-1)) ||
//     ((t2-1) > min(min(floord(-8*t1+pT-2,8),floord(8*t1+pN+7,8)),floord(pT+pN-2,16))) ||
//     (t3 < max(max(0,ceild(t1+(t2-1)-1,2)),ceild(16*(t2-1)-pN-14,16))) ||
//     (t3 > min(min(floord(pT+pN-2,16),floord(16*(t2-1)+pN+14,16)),floord(8*t1+8*(t2-1)+pN+15,16)));
//   bool condt3 = 
//     ((t3-1) < max(max(0,ceild(t1+t2-1,2)),ceild(16*t2-pN-14,16))) ||
//     ((t3-1) > min(min(floord(pT+pN-2,16),floord(16*t2+pN+14,16)),floord(8*t1+8*t2+pN+15,16)));

//   if (!condt1) {
//     c.M.get(TileTag(t1-1,  t2,  t3,  pT,  pN,  pN,  pN), wA);
//   } 
//   if (!condt2) {
//     c.M.get(TileTag(t1,  t2-1,  t3,  pT,  pN,  pN,  pN), wA);
//   } 
//   if (!condt3) {
//     c.M.get(TileTag(t1,  t2,  t3-1,  pT,  pN,  pN,  pN), wA);
//   } 

//   if (condt1 && condt2 && condt3) {
//     c.M.get(TileTag(DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, pT,  pN, pN, pN), wA);
//   }

//   double (*M)[2][Npadded][Npadded][Npadded] = (double (*)[2][Npadded][Npadded][Npadded])(wA.M);

//   int64_t t4, t5, t6, t7, t8, lbv, ubv;
//   for (t4=max(max(max(0,ceild(t1+t2-124,125)),ceild(16*t2-pN-998,1000)),ceild(16*t3-pN-998,1000));t4<=min(min(min(min(floord(8*t1+pN+7,500),floord(pT+pN-2,1000)),floord(16*t2+pN+14,1000)),floord(16*t3+pN+14,1000)),floord(8*t1+8*t2+pN+15,1000));t4++) {
//     for (t5=max(max(max(max(max(0,8*t1+8*t2),16*t1+1),16*t2-pN),16*t3-pN),1000*t4-pN);t5<=min(min(min(min(min(pT-2,16*t2+14),16*t3+14),1000*t4+998),8*t1+8*t2+15),16*t1+pN+15);t5++) {
//       if (t5%2 == 0) {
//         for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
//           for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
//             lbv=max(1000*t4,t5+1);
//             ubv=min(1000*t4+999,t5+pN);
//             double *m0 = &M[0][0][-t5+t6][-t5+t7][-t5];
//             double *m1 = &M[0][1][-t5+t6][-t5+t7][-t5];
//             double *m0m100 = &M[0][0][-t5+t6-1][-t5+t7][-t5];
//             double *m0m010 = &M[0][0][-t5+t6][-t5+t7-1][-t5];
//             double *m0p100 = &M[0][0][-t5+t6+1][-t5+t7][-t5];
//             double *m0p010 = &M[0][0][-t5+t6][-t5+t7+1][-t5];
// #pragma vector always
// #pragma ivdep
//             for (t8=lbv;t8<=ubv;t8++) {
//               *(m1 + t8) = 
//                 0.125*(*(m0p100 + t8)-2.0*(*(m0 + t8))+*(m0m100 + t8))+
//                 0.125*(*(m0p010 + t8)-2.0*(*(m0 + t8))+*(m0m010 + t8))+
//                 0.125*(*(m0 + t8 - 1)-2.0*(*(m0 + t8))+*(m0 + t8 + 1));;
//             }
//           }
//         }
//       }else{
//         for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
//           for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
//             lbv=max(1000*t4,t5+1);
//             ubv=min(1000*t4+999,t5+pN);
//             double *m0 = &M[0][0][-t5+t6][-t5+t7][-t5];
//             double *m1 = &M[0][1][-t5+t6][-t5+t7][-t5];
//             double *m1m100 = &M[0][0][-t5+t6-1][-t5+t7][-t5];
//             double *m1m010 = &M[0][0][-t5+t6][-t5+t7-1][-t5];
//             double *m1p100 = &M[0][0][-t5+t6+1][-t5+t7][-t5];
//             double *m1p010 = &M[0][0][-t5+t6][-t5+t7+1][-t5];
// #pragma ivdep
// #pragma vector always
//             for (t8=lbv;t8<=ubv;t8++) {
//               *(m0 + t8) = 
//                 0.125*(*(m1p100 + t8)-2.0*(*(m1 + t8))+*(m1m100 + t8))+
//                 0.125*(*(m1p010 + t8)-2.0*(*(m1 + t8))+*(m1m010 + t8))+
//                 0.125*(*(m1 + t8 - 1)-2.0*(*(m1 + t8))+*(m1 + t8 + 1));;
//             }
//           }
//         }
//       }
//     }
//   }
//   c.M.put(TileTag(t1,  t2,  t3,  pT,  pM,  pN,  pP), M);  
  
//   return CnC::CNC_Success;
// }


void Stencil::operator()() {
   stencil_context c;

   CnC::debug::collect_scheduler_statistics( c );
#ifdef DEBUG
   CnC::debug::trace( c.m_tags );
   CnC::debug::trace( c.M );
#endif //DEBUG   
   tbb::tick_count tick = tbb::tick_count::now();

   c.M.put(TileTag(DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, pT, pM, pN, pP), M);

   int64_t t1, t2, t3;
   for (t1=ceild(-pN-15,16);t1<=floord(pT-3,16);t1++) {
     for (t2=max(t1,-t1-1);
         t2<=min(min(floord(-8*t1+pT-2,8),floord(8*t1+pN+7,8)),floord(pT+pN-2,16));t2++) {
       for (t3=max(max(0,ceild(t1+t2-1,2)),ceild(16*t2-pN-14,16));
            t3<=min(min(floord(pT+pN-2,16),floord(16*t2+pN+14,16)),floord(8*t1+8*t2+pN+15,16));
            t3++) {
         c.m_tags.put(TileTag(t1, t2, t3,  pT,  pN,  pN, pN));
       }
     }
   }
   
   // Wait for all steps to finish
   c.wait();
   
   printf("Total time for init and parallel execution of %ld iterations of a"	\
	  "stencil of size %ld x %ld x %ld: %g sec\n", pT, pN, pN, pN, (tbb::tick_count::now()-tick).seconds());
}



template <>
size_t cnc_tag_hash_compare< TileTag >::hash(const TileTag& tt) const
{
  return ( (tt[0]) << 19 ) + ( tt[1] << 7 ) + tt[2];
}

template <>
bool cnc_tag_hash_compare< TileTag >::equal(const TileTag& a, const TileTag& b) const
{
  return a == b;
}

std::ostream & cnc_format( std::ostream& os, const TileTag & t )
{
  os << "(" << t[0] << "," << t[1] << "," << t[2] << "," << ")";
  return os;
}

ostream& operator<< (ostream& stream, const TileTag &tt) {
  for (int64_t i=0; i<7; i++)
    stream << tt[i] << " ";

  return stream;
}
